package model;

/**
 * 
 * LevelManager class
 * 
 * Contains the majority of the game data and methods for updating the game. 
 * A new LevelManager object is created every time a new level is loaded by the
 * Model.
 * 
 */

public class LevelManager {

	private int worldHead = 0;
	private int levelNumber;
	private boolean isPaused = false;
	private boolean deadLemon = false;
	private static final int BLOCK_SIZE = 50;
	private static final int FRAME_WIDTH = 800;
	private static final int FRAME_HEIGHT = 600;
	private boolean movingRight = false;
	private boolean movingLeft = false;
	private LevelEntity blockList[][];
	private int gravity;
	private PlayerCharacter pc;

	/**
	 * LevelManager
	 * 
	 * creates a levelManager object based upon the passed in parameter
	 * 
	 * @param levelGravity
	 * @param spawnHeight
	 * @param levelBlocks
	 * @param level
	 */
	public LevelManager(int levelGravity, int spawnHeight,
			LevelEntity levelBlocks[][], int level) {

		pc = new PlayerCharacter(2, spawnHeight, gravity * gravity);
		blockList = levelBlocks;
		this.levelNumber = level;
	}

	/**
	 * checkDeath
	 * 
	 * returns status of the playerCharacter, ie. alive or dead
	 * 
	 * @return
	 */
	public boolean checkDeath() {
		return deadLemon;
	}

	/**
	 * checkFalling
	 * 
	 * checks if the playerCharacter is falling and updates the playerCharacter
	 * accordingly
	 */
	private void checkFalling() {
		int xPosition = this.worldHead + FRAME_WIDTH / 2;
		int yPosition = pc.getYPosition();
		int yBlock = 11 - (FRAME_HEIGHT - yPosition) / BLOCK_SIZE;
		// findFloor will return a block value based upon an upper left
		// coordinate System
		int floorPosition = findFloor(xPosition, yPosition);
		if (pc.getJumpState() == 1) {
		} else {
			if (yBlock < floorPosition || floorPosition == -1) {
				pc.setJumpState(-1);
			} else {
				pc.setJumpState(0);
			}
		}
	}

	/**
	 * entityCollision
	 * 
	 * Detects if the given (x,y) position exists within a level entity X and Y
	 * should be as world coordinates not block coordinates
	 * 
	 * @param xPos
	 * @param yPos
	 * @return
	 */
	public boolean entityCollision(int xPos, int yPos) {
		if (yPos > 600) {
			deadLemon = true;
			return true;
		}
		int column = (1 + (this.worldHead + xPos) / BLOCK_SIZE)
				% blockList.length;
		int row = 11 - yPos / BLOCK_SIZE;
		if (blockList[column][row] != null) {
			return true;
		}
		return false;
	}

	/**
	 * findFloor
	 * 
	 * Retrieves the height of the highest levelEntity below the given (x,y)
	 * position. Returns -1 if no block exists (hole) returns the block location
	 * in terms or a Upper Left Coordinate System not yPosition
	 * 
	 * @param xPosition
	 * @param yPosition
	 * @return
	 */
	public int findFloor(int xPosition, int yPosition) {
		int column = (1 + (xPosition / BLOCK_SIZE)) % blockList.length;
		int row = (FRAME_HEIGHT - yPosition) / BLOCK_SIZE;
		// row and column have the origin at the upper left
		for (int i = row; i >= 0; i--) {
			// BlockList has coordinates on 0,0 starting origin at the lower
			// Left
			if (blockList[column][i] != null) {
				return ((FRAME_HEIGHT - 2 * BLOCK_SIZE) / BLOCK_SIZE) - i;
			}
		}
		return -1;
	}

	/**
	 * getBlocks
	 * 
	 * returns the array of levelEntities
	 * 
	 * @return
	 */
	public LevelEntity[][] getBlocks() {
		LevelEntity blocks[][] = new LevelEntity[19][12];
		int head = worldHead / 50;
		for (int i = 0; i < 18; i++) {
			for (int j = 0; j < 11; j++) {
				blocks[i][j] = blockList[head % blockList.length][j];
			}
			head++;
		}
		return blocks;
	}

	/**
	 * getCurrentLemonSprite
	 * 
	 * returns an integer indicating the current sprite being used by the
	 * playerCharacter
	 * 
	 * @return
	 */
	public int getCurrentLemonSprite() {
		return pc.getSprite();
	}

	/**
	 * getEntityAt
	 * 
	 * returns the levelEntity at a specified location
	 * 
	 * @param xPos
	 * @param yPos
	 * @return
	 */
	public LevelEntity getEntityAt(int xPos, int yPos) {

		int row = 11 - yPos / BLOCK_SIZE;
		int column = (1 + (this.worldHead + xPos) / BLOCK_SIZE)
				% blockList.length;
		return blockList[column][row];
	}

	/**
	 * getHead
	 * 
	 * returns the current world head
	 * 
	 * @return
	 */
	public int getHead() {
		return this.worldHead;
	}

	/**
	 * getHealth
	 * 
	 * returns the current health of the playerCharacter
	 * 
	 * @return
	 */
	public int getHealth() {
		return pc.getHealth();
	};

	/**
	 * getLevelLength
	 * 
	 * returns the length of the current level
	 * 
	 * @return
	 */
	public int getLevelLength() {
		return blockList.length;
	}

	/**
	 * getLevelNumber
	 * 
	 * returns the current level number that is being played
	 * 
	 * @return
	 */
	public int getLevelNumber() {
		return this.levelNumber;
	}

	/**
	 * getPauseState
	 * 
	 * returns if the game is currently paused
	 * 
	 * @return
	 */
	public boolean getPauseState() {
		return isPaused;
	}

	/**
	 * getPlayerHeight
	 * 
	 * returns the yPosition of the playerCharacter in pixels
	 * 
	 * redundant function see getYPosition
	 * 
	 * @return
	 */
	public int getPlayerHeight() {
		return pc.getYPosition();
	}

	/**
	 * getWorldHead
	 * 
	 * returns the world head
	 * 
	 * @return
	 */
	public int getWorldHead() {
		return this.worldHead;
	}

	/**
	 * getYPosition
	 * 
	 * returns the yPosition of the playerCharacter in pixels
	 * 
	 * @return
	 */
	public int getYPosition() {
		return pc.getYPosition();
	}

	/**
	 * loadLevelData
	 * 
	 * currently does nothing
	 * 
	 * @param level
	 * @return
	 */
	public boolean loadLevelData(int level) {
		// @TODO needs an importer for level files
		return false;
	}

	/**
	 * moveJump
	 * 
	 * tells the playerCharacter that need to jump
	 */
	public void moveJump() {
		if (!isPaused)
			pc.Jump();
	}

	/**
	 * moveLeft
	 * 
	 * tells the playerCharacter they that it is moving left
	 */
	public void moveLeft() {
		this.movingRight = false;
		this.movingLeft = true;
		pc.setFacing(false);
	}

	/**
	 * moveRight
	 * 
	 * tells the playerCharacter they that it is moving right
	 */
	public void moveRight() {
		this.movingRight = true;
		this.movingLeft = false;
		pc.setFacing(true);
	}

	/**
	 * moveStop
	 * 
	 * stops the movement of the playerCharacter
	 */
	public void moveStop() {
		this.movingRight = false;
		this.movingLeft = false;
	}

	/**
	 * setEntityAt
	 * 
	 * sets or removes an entity at a given location within the block array
	 * 
	 * @param xPos
	 * @param yPos
	 * @param entity
	 */
	public void setEntityAt(int xPos, int yPos, LevelEntity entity) {

		int row = 11 - yPos / BLOCK_SIZE;
		int column = (1 + (this.worldHead + xPos) / BLOCK_SIZE)
				% blockList.length;
		blockList[column][row] = entity;
	}

	/**
	 * togglePause
	 * 
	 * toggles the paused state on and off
	 */
	public void togglePause() {
		isPaused = !isPaused;
	}

	/**
	 * update
	 * 
	 * calls the updateHorizontal and updateVertical functions if the game is
	 * not paused
	 */
	public void update() {
		if (!isPaused) {
			updateHorizontal();
			updateVertical();
		}
	}

	/**
	 * updateHorizontal
	 * 
	 * does all of the calculations to detect any collisions between any
	 * horizontal blocks and will update the horizontal position accordingly
	 */
	private void updateHorizontal() {
		int newX = 0;
		if (this.movingRight == true && this.movingLeft == false) {
			newX = 8;
			if (this.entityCollision((FRAME_WIDTH / 2) + BLOCK_SIZE / 2,
					pc.getYPosition())) {
				int damage = getEntityAt(
						(FRAME_WIDTH / 2) + newX + BLOCK_SIZE / 2,
						pc.getYPosition()).getHostileDamage();
				if (damage > 0) {
					newX = -25;
					movingRight = false;
					pc.setJumpState(-1);
					if (!pc.updateHealth(damage)) {
						deadLemon = true;
					}
				} else if (damage < 0) {
					pc.updateHealth(damage);
					setEntityAt((FRAME_WIDTH / 2) + newX + BLOCK_SIZE / 2,
							pc.getYPosition(), null);
				} else
					newX = 0;
			}
		} else if (this.movingLeft == true && this.movingRight == false) {
			newX = -8;
			if (this.entityCollision((FRAME_WIDTH / 2) - BLOCK_SIZE / 2,
					pc.getYPosition())) {
				int damage = getEntityAt(
						(FRAME_WIDTH / 2) + newX - BLOCK_SIZE / 2,
						pc.getYPosition()).getHostileDamage();
				if (damage > 0) {
					newX = 25;
					movingRight = true;
					pc.setJumpState(-1);
					if (!pc.updateHealth(damage)) {
						deadLemon = true;

					}
				} else if (damage < 0) {
					pc.updateHealth(damage);
					setEntityAt((FRAME_WIDTH / 2) + newX + BLOCK_SIZE / 2,
							pc.getYPosition(), null);
				} else
					newX = 0;
			}
		}

		if (this.worldHead + newX > 0) {
			this.worldHead += newX;
		} else
			this.worldHead = 0;
		if (movingRight || movingLeft) {
			pc.updateSprite();
		}
	}

	/**
	 * updateVertical
	 * 
	 * does all of the calculations to detect any collisions between any
	 * vertical blocks and will update the vertical position accordingly
	 */
	private void updateVertical() {
		// newY will be the difference in Pixels Coordinates are Origin Upper
		// Left
		checkFalling();
		int newY = pc.updateJump();
		if (newY <= 0)
			newY = 1;
		int state = pc.getJumpState();
		switch (state) {
		case 1: {
			if (!entityCollision((FRAME_WIDTH / 2), newY)) {
				pc.setYPosition(newY);
				return;
			} else
				break;
		}
		case 0: {
			if (!entityCollision((FRAME_WIDTH / 2), newY + BLOCK_SIZE)) {
				pc.setYPosition(newY - newY % BLOCK_SIZE + 1);
				pc.setJumpState(0);
				return;
			} else
				break;
		}
		case -1: {
			if (!entityCollision((FRAME_WIDTH / 2), newY)) {
				pc.setYPosition(newY);
				return;
			} else
				break;
		}
		}
		int newX = 0;
		switch (state) {
		case 0: {
			int damage = getEntityAt((FRAME_WIDTH / 2), newY + BLOCK_SIZE)
					.getHostileDamage();
			if (damage > 0) {
				if (pc.getFacing()) {
					newX = -25;
				} else
					newX = 25;
				if (!pc.updateHealth(damage)) {
					deadLemon = true;
				}
			} else if (damage < 0) {
				pc.updateHealth(damage);
				setEntityAt((FRAME_WIDTH / 2), newY + BLOCK_SIZE, null);
			}
			pc.setYPosition(newY - newY % BLOCK_SIZE + 1);
			break;
		}
		case 1: {
			int damage = getEntityAt((FRAME_WIDTH / 2), newY)
					.getHostileDamage();
			if (damage > 0) {
				if (pc.getFacing()) {
					newX = -25;
				} else
					newX = 25;
				if (!pc.updateHealth(damage)) {
					deadLemon = true;
				}
			} else if (damage < 0) {
				pc.updateHealth(damage);
				setEntityAt((FRAME_WIDTH / 2), newY + BLOCK_SIZE, null);
			}
			pc.setJumpState(-1);
			this.moveStop();
			break;
		}
		}
		if (this.worldHead + newX > 0) {
			this.worldHead += newX;
		} else
			this.worldHead = 0;
		if (movingRight || movingLeft) {
			pc.updateSprite();
		}
	}
}